using System;
using System.Collections.Generic;
using System.Text;

namespace XiVo.PCRComm
{
  public enum CommandNumberEnum : byte
  {
    powerUp = 0x00,
    powerDown = 0x01,
    getRadioID = 0x31,
    setMute = 0x13,
    changeChannel = 0x10, // first data byte is 02,
    getChannelInfo = 0x25,
    getExtendedChannelInfo = 0x22,
    getSignalData = 0x43,
    monitorChannelData = 0x50,
    hello = 0x74
  }

  public enum ResponseNumberEnum : byte
  {
    powerUp = 0x80,
    powerDown = 0x81,
    getRadioID = 0xB1,
    setMute = 0x93,
    changeChannel = 0x90,
    getChannelInfo = 0xA5,
    getExtendedChannelInfo = 0xA2,
    getSignalData = 0xC3,
    monitorChannelData = 0xD0,
    channelNameChange = 0xd1,
    channelGenreChange = 0xd2,
    channelArtistTitleChange = 0xd3,
    channelSongTime = 0xd6,
    channelExtArtistChange = 0xd4,
    channelExtTitleChange = 0xd5,
    listenMode = 0xF5,
    radioOff = 0xff
  }

  public class PCREventArgs : EventArgs
  {
    public int length;
    public ResponseNumberEnum responseNumber;
    public ChannelInfo channelInfo;
  }

  public class PCRIO
  {
    private Queue<byte[]> commandQueue = new Queue<byte[]>();

    private System.IO.Ports.SerialPort port;
    Random rand = new Random();
    ASCIIEncoding enc = new ASCIIEncoding();
    public delegate void responseReceived(PCRIO sender, PCREventArgs e);
    public event responseReceived responseReceivedEvent;
    public volatile bool done = false;
    public RingBuffer recData = new RingBuffer();
    public System.Threading.Thread commWorkerThread;
    Queue<ChannelInfo> channelInfoList = new Queue<ChannelInfo>();
    bool gettingAllChannels = false;
    int currentChannel;
    public bool WantRadioOn = true;
    public ChannelList channels = new ChannelList();

    public int CurrentChannel
    {
      get { return currentChannel; }
      set
      {
        ChangeChannel(value);
      }
    }
    public int commandsWaiting
    {
      get
      {
        return commandQueue.Count;
      }
    }


    public PCRIO(string portName)
    {
      port = new System.IO.Ports.SerialPort(portName);
      port.ReadBufferSize = 16384;
      try
      {
        port.Open();
        port.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived);
      }
      catch (Exception ex)
      {
        // we will just "not work" if the port can't be opened.
      }

      //commWorkerThread = new System.Threading.Thread(MainLoop);
      //commWorkerThread.Start();
    }

    void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
      readBuffer();
      handleResponse();
      sendNextCommand();
    }

    public void Close()
    {
      if (commWorkerThread != null)
        commWorkerThread.Abort();
      port.Close();
    }

    public void powerUp()
    {
      WantRadioOn = true;
      //byte[] data = new byte[] {
      //  0x00, 0x10, 0x10, 0x24, 0x01 };
      byte[] data = new byte[] {
        0x00, 0x10, 0x10, 0x10, 0x01 };
      queueCommand(data);
    }

    public void powerDown()
    {
      WantRadioOn = false;
      byte[] data = new byte[] {
        0x01, 0x00 };
      queueCommand(data);
    }

    public void getRadioID()
    {
      byte[] data = new byte[] {
        0x31 };
      queueCommand(data);
    }

    public void setMute(byte mute)
    {
      byte[] data = new byte[] {
        0x13, mute };
      queueCommand(data);
    }

    public void ChangeChannel(int channel)
    {
      currentChannel = channel;
      queueCommand(new byte[] { 0x10, 0x02, (byte)channel, 0x00, 0x00, 0x01 });
    }

    public void MonitorChannelData(int channel, byte enabled)
    {
      queueCommand(new byte[] { 0x50, (byte)channel, 0x00, 0x00, enabled, enabled });
    }

    public void EnqueueChannelInfoRequest(PCRComm.ChannelInfo info)
    {
      if (!gettingAllChannels)
        channelInfoList.Enqueue(info);
    }

    public void startChannelInfoRequest()
    {
      if (!gettingAllChannels && channelInfoList.Count > 0)
        getChannelInfo(channelInfoList.Dequeue());
    }

    public void getChannelInfo(PCRComm.ChannelInfo info)
    {
      getChannelInfo(info.channel);
    }

    public void getAllChannelInfo()
    {
      gettingAllChannels = true;
      getChannelInfo(0, true);
    }

    public void getChannelInfo(int channel)
    {
      if (!gettingAllChannels)
        getChannelInfo(channel, false);
    }

    private void getChannelInfo(int channel, bool gettingNext)
    {
      byte mode;
      if (gettingNext)
        mode = 9;
      else
        mode = 8;
      byte[] data = new byte[] {
        0x25, mode, (byte) channel, 0 };
      queueCommand(data);
    }

    public void getExtendedChannelInfo(int channel)
    {
      queueCommand(new byte[] { 0x22, (byte)channel });
    }

    public void getSignalData()
    {
      byte[] data = new byte[] {
        0x43 };
      queueCommand(data);
    }

    virtual public void queueCommand(byte[] data)
    {
      byte[] buffer;
      int length = data.Length + 6;
      buffer = new byte[length];
      buffer[0] = 0x5a;
      buffer[1] = 0xa5;
      buffer[2] = (byte)(data.Length / 256);
      buffer[3] = (byte)(data.Length & 0x00ff);
      System.Array.Copy(data, 0, buffer, 4, data.Length);
      buffer[length - 2] = 0xed;
      buffer[length - 1] = 0xed;

      commandQueue.Enqueue(buffer);
      //if (recData.Count > 7)
      //  loopCycle();
      sendNextCommand();
    }

    public void sendNextCommand()
    {
      while (commandQueue.Count > 0)
      {
        byte[] buffer = commandQueue.Dequeue();
        if (port.IsOpen)
          port.Write(buffer, 0, buffer.Length);
      }
    }

    public void displayBuffer(int len)
    {
      int i = 0;
      while (i < len)
      {
        System.Diagnostics.Debug.Write(i.ToString("X2") + " : ");
        for (int j = i; j < i + 16; ++j)
        {
          System.Diagnostics.Debug.Write(recData[j].ToString("X2"));
          System.Diagnostics.Debug.Write(' ');
        }
        System.Diagnostics.Debug.Write(": ");
        for (int j = i; j < i + 16; ++j)
        {
          char c = (char)recData[j];
          if (c < ' ' || c > '~')
            c = '.';
          System.Diagnostics.Debug.Write(c);
        }
        i += 16;
        System.Diagnostics.Debug.WriteLine(" :");
      }
      System.Diagnostics.Debug.WriteLine("");
    }

    public void MainLoop()
    {
      System.Diagnostics.Debug.WriteLine("Serial worker thread starting");
      while (!done)
      {
        loopCycle();
        //System.Windows.Forms.Application.DoEvents();
        System.Threading.Thread.Sleep(10);
      }
      System.Diagnostics.Debug.WriteLine("Serial worker thread complete.");
    }

    void loopCycle()
    {
      readBuffer();
      handleResponse();
      sendNextCommand();
    }

    void readBuffer()
    {
      while (port.BytesToRead != 0)
        recData.Write((byte)port.ReadByte());
    }

    private void handleResponse()
    {
      int length;
      while (recData.Count > 7)
      {
        while (recData[0] != 0x5a || recData[1] != 0xa5)
        {
          recData.Read();
          if (recData.Count < 7)
            return;
        }
        length = recData[2] * 256 + recData[3];
        if (recData.Count < length + 6)
          return;

        recData.Discard(4);
        PCREventArgs e = new PCREventArgs();
        e.length = length;
        e.responseNumber = (ResponseNumberEnum)recData[0];
        switch (e.responseNumber)
        {
          case ResponseNumberEnum.powerUp:
            break;
          case ResponseNumberEnum.powerDown:
            break;
          case ResponseNumberEnum.getChannelInfo:
            e.channelInfo = receivedChannelInfo();
            channels.UpdateChannelInfo(e.channelInfo);
            break;
          case ResponseNumberEnum.getExtendedChannelInfo:
            e.channelInfo = receivedExtendedChannelInfo();
            channels.UpdateChannelInfo(e.channelInfo);
            break;
          case ResponseNumberEnum.changeChannel:
            //displayBuffer(16);
            currentChannel = recData[4];
            e.channelInfo = channels.channel(currentChannel);
            e.channelInfo.channel = currentChannel;
            break;
          case ResponseNumberEnum.channelArtistTitleChange:
            e.channelInfo = ArtistTitleChanged();
            break;
          case ResponseNumberEnum.radioOff:
            if (WantRadioOn)
              powerUp();
            break;
          case ResponseNumberEnum.monitorChannelData:
            break;
          default:
            System.Diagnostics.Debug.WriteLine("Response not handled: " + recData[0].ToString("X"));
            displayBuffer(length);
            break;
        }
        recData.Discard(e.length + 2);
        if (responseReceivedEvent != null)
        {
          responseReceivedEvent(this, e);
        }
      }
    }

    ChannelInfo receivedChannelInfo()
    {
      ChannelInfo i = new ChannelInfo();
      i.channel = recData[3];
      //if (i.channel == CurrentChannel) displayBuffer(recData.Count);
      if (recData[2] != 0) // || recData[3] != recData[4])
      {
        System.Diagnostics.Debug.WriteLine(
          "Data[2]:" + recData[2].ToString()
          + " Data[3]: " + recData[3].ToString()
          + " Data[4]: " + recData[4].ToString());
        //displayBuffer(73);
        //i.valid = false;
      }
      else
        i.valid = true;
      if (recData[5] != 0)
        i.station = recData.getString(6, 16);
      if (recData[23] != 0)
        i.genre = recData.getString(24, 16);
      if (recData[40] != 0)
      {
        i.artist = recData.getString(41, 16);
        i.title = recData.getString(57, 16);
      }

      // if we're getting a range of channels, get the next one
      if (channelInfoList.Count > 0)
        startChannelInfoRequest();
      else if (i.channel > 0 && gettingAllChannels)
        getChannelInfo(i.channel, true);
      else
      {
        if (i.channel == currentChannel)
          getExtendedChannelInfo(currentChannel);
        gettingAllChannels = false;
      }

      return i;
    }

    ChannelInfo receivedExtendedChannelInfo()
    {
      ChannelInfo i = null;
      //displayBuffer(recData.Count);
      if (recData[1] != 0x0)
      {
        i = channels.channel(recData[3]);
        if (recData[4] != 0x00)
          i.artist = recData.getString(5, 32);
        if (recData[37] != 0x00)
          i.title = recData.getString(38, 32);

        if (recData[4] != 0x00 || recData[37] != 0x00)
        {
          System.Diagnostics.Debug.WriteLine("RECEIVED GOOD EXT CHANNEL DATA");
          displayBuffer(recData.Count);
        }
      }
      return i;
    }

    ChannelInfo ArtistTitleChanged()
    {
      ChannelInfo i = new ChannelInfo();
      i.valid = true;
      i.channel = recData[1];
      i.station = "";
      i.genre = "";
      i.artist = recData.getString(3, 16);
      i.title = recData.getString(16, 16);

      getExtendedChannelInfo(i.channel);
      return i;
    }

    String trimNull(String text)
    {
      return text.TrimEnd('\0').Trim();
    }

    public void Hello()
    {
      byte[] data = new byte[] {
        0x74, 0x00, 0x01 };
      queueCommand(data);
    }

  }
}
